//
//  BBDistortionMap.m
//  BBTouch
//
//  Created by ben smith on 8/12/08.
//  This file is part of BBTouch.
//
//  BBTouch is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  BBTouch is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.

//  You should have received a copy of the GNU Lesser General Public License
//  along with BBTouch.  If not, see <http://www.gnu.org/licenses/>.
// 
//  Copyright 2008 Ben Britten Smith ben@benbritten.com .
//

#import "BBDistortionMap.h"
#import "BBDistortionMesh.h"


@implementation BBDistortionMap

@synthesize sourceMesh;
@synthesize destinationMesh;
@synthesize mapFrame;
@synthesize dirty;

- (id) init
{
	self = [super init];
	if (self != nil) {
		self.dirty = YES;
	}
	return self;
}

// standard init with coder kinda method
- (id)initWithCoder:(NSCoder *)coder
{
	if (![self init]) return nil;
	self.sourceMesh = [coder decodeObjectForKey:@"sourceMesh"];
	self.destinationMesh = [coder decodeObjectForKey:@"destinationMesh"];
	self.mapFrame = [coder decodeRectForKey:@"mapFrame"];
	self.dirty = YES;
	return self;
}

// encode myself
- (void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:sourceMesh forKey:@"sourceMesh"];
	[coder encodeObject:destinationMesh forKey:@"destinationMesh"];
	[coder encodeRect:mapFrame forKey:@"mapFrame"];
}


// generates a dictionary of our current values
// this is like encodeWithCoder, but I am using dictionaries instead of
// encoders because I want an XML file at the end of it
-(NSDictionary*)propertyDictionary
{
	// make a new dict for all my stuff
	NSMutableDictionary * props = [NSMutableDictionary dictionary];
	[props setObject:[sourceMesh propertyDictionary] forKey:@"sourceMesh"];
	[props setObject:[destinationMesh propertyDictionary] forKey:@"destinationMesh"];
	[props setObject:NSStringFromRect(mapFrame) forKey:@"mapFrame"];
	
	return props;
}


// this can be slooooow!
-(void)calculateMap
{	
	[map release];
	// builds a one-to-one map for all the points in the mapFrame
	map = [[NSMutableArray alloc] init];

	NSInteger loopX,loopY;
	NSInteger yMin = NSMinY(mapFrame);
	NSInteger yMax = NSMaxY(mapFrame);
	NSInteger xMax = NSMaxX(mapFrame);
	NSInteger xMin = NSMinX(mapFrame);
	NSPoint srcPoint,destPoint;
	NSRect meshBounds = [destinationMesh boundingRect];
	
	for (loopY = yMin; loopY < yMax; loopY++) {
		for (loopX = xMin; loopX < xMax; loopX++) {
			srcPoint = NSMakePoint(loopX, loopY);
			// if it isnt in the mesh bounds then we dont really care
			if (!NSPointInRect(srcPoint, meshBounds)) {
				[map addObject:@"NA"];
				continue;
			}
			if ([self _calculateDestinationPoint:&destPoint fromSourcePoint:srcPoint]) {
				// great, got one, jam it into the array where we are now
				[map addObject:[NSValue valueWithPoint:destPoint]];
			} else {
				// in the mesh bounds, but not in the mesh, put in a placeholder
				[map addObject:@"NA"]; // no use making this a big string, just to take up lots of memory
			}
		}
	}
	self.dirty = NO;
}


-(BOOL)normalizedDestinationPoint:(NSPoint*)destPoint fromSourcePoint:(NSPoint)p
{
	NSPoint normal;
	if (![self destinationPoint:&normal fromSourcePoint:p]) return NO;
	
	normal.x -= NSMinX(mapFrame);
	normal.y -= NSMinY(mapFrame);
	normal.x /= NSWidth(mapFrame);
	normal.y /= NSHeight(mapFrame);
	*destPoint = normal;
	return YES;
}

// this is the 'money' method. it is what you will call over and over again to
// use your meshes, returns yes if the point is in the mesh
-(BOOL)destinationPoint:(NSPoint*)destPoint fromSourcePoint:(NSPoint)p
{
	
	// we need to invert the Y because of the way the 
	// images come off the sequencer
	
	// if the map is nil, then we havent calced it yet
	// so we do this the next best way, on the fly
//	if (map == nil || dirty) {
		p.y = NSMaxY(self.mapFrame) - p.y; 
		return [self _calculateDestinationPoint:destPoint fromSourcePoint:p];
//	}

	//NOTE: the prebuilt table lookup map is still busted
	
	// otherwise use the much much faster map
	NSInteger index = p.y * NSWidth(mapFrame) + p.x;
	if (index > [map count]) //we have asked for a point out of our frame
		return NO;
	
	id obj = [map objectAtIndex:index];

	if ([obj isKindOfClass:[NSString class]]) {
		// then the point is in the frame, but not in the mesh
		return NO;
	}
	*destPoint = [(NSValue*)obj pointValue];
	return YES;
}

// this is the 'slow' private method that is used to build the map,
// or if there is no map calculated yet
-(BOOL)_calculateDestinationPoint:(NSPoint*)destPoint fromSourcePoint:(NSPoint)p
{
	// first do a hit test in the source mesh
	NSArray * boundingTriangleVertices = [sourceMesh boundingTriangleForPoint:p];
	// not in the mesh? return NO
	if (boundingTriangleVertices == nil) return NO;
	// in the mesh? great, now do the interpolation on the destination mesh
	*destPoint = [destinationMesh destinationPointFromSourcePoint:p sourceVertices:boundingTriangleVertices];
	return YES;
}

@end
